﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace CodePlex.Resourceful.Amazon
{
    public class QueryAPIClient : Client
    {
       
        private readonly string _accessKeyID;
        private readonly string _secretAccessKey;
        private readonly string _method;
        private readonly string _version;

        protected QueryAPIClient(string accessKeyID, string secretAccessKey, bool useSSL, string method, string serviceHost, string version)
            : base(new Uri((useSSL ? "https" : "http") + "://" + serviceHost))
        {
            _accessKeyID = accessKeyID;
            _secretAccessKey = secretAccessKey;

            _method = method;
            _version = version;
        }



        protected ClientResponse DoQueryRequest(string path, NameValuePairs parameters)
        {

            parameters["Version"] = _version;
            parameters["SignatureMethod"] = "HmacSHA256";
            parameters["Timestamp"] = Utils.Iso8601Date(DateTime.UtcNow);
            parameters["AWSAccessKeyId"] = _accessKeyID;
            parameters["SignatureVersion"] = "2";
            parameters["Signature"] = ComputeSignatureVersion2(_method, path, parameters);

            NameValuePairs headers = new NameValuePairs();
            Stream inputStream = null;
            if (_method == HttpMethod.Get)
                path = Utils.EncodeUrlPath(path, parameters);
            if (_method == HttpMethod.Post)
            {
                headers[HttpHeader.ContentType] = "application/x-www-form-urlencoded; charset=utf-8"; // MediaType.ApplicationExWwwFormUrlencoded;
              // string body = CanonicalizedQueryString(parameters).Replace("%20", "+");
                string body = Utils.EncodeUrlPath( parameters);
                inputStream = Utils.ToStream(body);
            }
            if (path.StartsWith("/"))
                path = path.Substring(1);

            ClientRequest request = ClientRequest.Create(_method, path, headers, inputStream);

            return this.DoRequest(request);
        }

        private string Encode(string value)
        {
            if (value == null)
                return null;
            //Do not URL encode any of the unreserved characters that RFC 3986 defines.
            //These unreserved characters are A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ).
            //Percent encode all other characters with %XY, where X and Y are hex characters 0-9 and uppercase A-F.
            //Percent encode extended UTF-8 characters in the form %XY%ZA....
            //Percent encode the space character as %20 (and not +, as common encoding schemes do).

            StringBuilder rt = new StringBuilder();
            foreach (char c in value)
            {
                if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.' || c == '~')
                {
                    rt.Append(c);
                }
                else
                {
                    byte[] bytes = Encoding.UTF8.GetBytes(new char[] { c });
                    foreach (byte b in bytes)
                    {
                        rt.AppendFormat("%{0:X2}", b);
                    }
                }
            }


            return rt.ToString();


        }

        private string ComputeSignatureVersion2(string httpVerb, string path, NameValuePairs parameters)
        {
            httpVerb = httpVerb.ToUpper();
            string hostheaderInLowerCase = this.RootUri.Host.ToLower();
            string canonicalizedQueryString = CanonicalizedQueryString(parameters);

            path = path ?? string.Empty;
            if (!path.StartsWith("/"))
                path = "/" + path;

            string stringToSign = httpVerb + "\n" +
               hostheaderInLowerCase + "\n" +
               path + "\n" +
               canonicalizedQueryString;

            return Utils.ToBase64(Utils.ToHMACSHA256(Encoding.UTF8, Encoding.UTF8.GetBytes(_secretAccessKey), stringToSign));


        }
        private string CanonicalizedQueryString(NameValuePairs parameters)
        {
            List<string> keys = new List<string>(parameters.Keys);
            keys.Sort(delegate(string lhs, string rhs)
            {
                return BitConverter.ToString(Encoding.UTF8.GetBytes(lhs)).CompareTo(BitConverter.ToString(Encoding.UTF8.GetBytes(rhs)));
            });

            StringBuilder rt = new StringBuilder();
            foreach (string key in keys)
            {
                if (rt.ToString().Length > 0)
                    rt.Append('&');
                rt.Append(Encode(key) + "=" + Encode(parameters[key]));
            }
            return rt.ToString();
        }
    }
}
